Session 6: Upgrade the Model to Allow Shipments Between the Plants

In Session 5, you encountered a model for a production company that used multiple plants to produce their product. What you should note about that model is that while each of the different plants could be used to produce the products individually, all the selling and inventory was handled collectively, as a single source, for the whole company. You will now upgrade that model to allow each plant to sell the products, and maintain inventory individually. Furthermore, in order to fulfill the demand in the most efficient manner, the products can be shipped between the plants as needed.

To upgrade the model you will need to include two new alias indexes, toplant, and fromplant, which will represent the source and destination plants. You will also update the Inventory and Sales variables to include the plant index, as each plant can now sell the products and maintain inventory independent of each other.


New Concepts in This Session

Transportation Models

Models that allow shipping between locations are sometimes called transportation or distribution models. Typically, in transportation models, you have sources with certain availability, destinations with certain requirements, and you need to ship the products from these sources to these destinations. In some cases, you have transporation models with multiple levels. For example, there can be shipments from plants to depots, and then from the depots to retail stores.

Transshipment Models

Another group of distribution models are transshipment models. These models typically arise when you have multiple locations that both produce the goods and also act as demand centers. Since there are no specific sources and destinations, you can ship from any one location to any of the other locations.

Alias Indexes

Alias indexes are useful when you need to define a vector, which uses the same index more than once, as a subscript. When shipping products between plants you need to create a variable vector, representing how much to ship between the plants. Since the source plants and the destination plants come from the same set of plants, you need two alias indexes for the plants. The first alias index is needed to represent the source plants, and the second alias index is needed to represent the destination plants.

Using Where Conditions on Vector Variables

Sometimes, when working with multi-dimensional vector variables you will encounter cases where not all elements of the vector are valid, or have a meaning. For example, in transshipment models it would make little sense to ship a product from a certain plant back to that same plant. In these cases, you can use a WHERE condition on the variable to remove unnecessary elements.

For example, in a transshipment model you can eliminate the possibility of shipping to the same location by defining the variable as follows:

     VARIABLES
        Ship[fromplant,toplant] WHERE (fromplant <> toplant);
    

In this case, the condition (fromplant <> toplant) removes all of the vector elements, where the source plant is the same as the destination plant.

In some cases, elements need to be excluded that are not based on the values of the indexes. They then must be based on some data vector in the model. Typically, you have a cost vector containing how much it costs to ship between the plants. For those plants, if shipping between is not feasible, you can enter a special value for the cost, such as a zero, to be used to identify them. Then, in the variable definition, you can use this data vector to exclude the shipping routes that are not feasible as follows:

     VARIABLES
        Ship[fromplant,toplant]
           WHERE (ShipCost[fromplant,toplant] > 0);
    

Plant Balance Constraints

When working with transshipment models you need to ensure that the amount of products shipped to a plant, plus how much is produced and pulled from inventory, is equal to how much is shipped from the plant, plus how much is sold and put back into inventory. In short, everything that goes into the plant must be equal to everything that goes out of the plant. This kind of constraint is typically called a plant balance constraint. Here is an example of a simple plant balance constraint:

      PlantBal[plant, product, month]:
         Produce + Inventory[month-1]
       + SUM(fromplant: Ship[fromplant, toplant:=plant])
     =
         Sales + Inventory
       + SUM(toplant: Ship[fromplant:=plant, toplant]);
    

You will notice that this constraint is similar to the inventory balance constraint you have encountered in previous sessions. The only difference is that now you have to take into account that we are shipping to and from each plant by entering a summation over each plant for the Ship variable.

The index assignment 'toplant:=plant', in the first summation, allows us to specify that the toplant subscript should take the value of the plant subscript for the PlantBal constraint. This summation adds together all the shipments from each of the plants to the particular plant in that constraint. In similar manner, the index assignment 'fromplant:=plant', in the second summation, specifies that the fromplant subscript should take the value of the plant subscript.


Problem Description: Additions to Allow Shipments Between Plants

In this session, a new model will be created where each plant now acts as a separate demand center for the products, and can also keep inventory. You will use the model you created in Session 5, and make the necessary additions and updates to it.

Since each plant can sell the products, we now have a different demand for each plant, as well as for each product, and each month. The demand is given in the dbtable below:

This data has three dimensions, plants, products and months. In linear programming models it is quite typical to have data with multiple dimensions, possibly up to eight or more. In the next session, we will update the demand data to include one more dimension; machines, creating a four dimensional vector.

The inventory capacity is now different for each plant. We have four inventory capacity values, one for each plant, 800, 400, 500, and 400 respectively.

Since we now have multiple plants, each of which can maintain inventory, we now have different inventory costs for each plant, and each product. The new cost values for the inventory are shown here below:

Finally, since we are allowing shipments between the plants there are certain costs involved for shipping a product, as shown in the dbtable below:

As you can see, there are no values in the dbtable where the source plant is same as the destination plant. This is because there is no benefit in shipping back to the same plant.


Formulation of the Model in MPL

Listed below is the entire model formulation for Planning6. The additions to the model are highlighted in boldface in order to make it easy for you to see the changes from the model in Session 5.

     TITLE
         Production_Planning6;

     INDEX
         product   := (A1, A2, A3);
         month     := (Jan, Feb, Mar, Apr);
         plant     := (p1, p2, p3, p4);
         fromplant := plant;
         toplant   := plant;


     DATA
         Price[product]                := (120.00, 100.00, 115.00);
         Demand[plant, product, month] := DATAFILE("Demand6.dat");
         ProdCost[plant, product]      := DATAFILE("ProdCost.dat");
         ProdRate[plant, product]      := DATAFILE("ProdRate.dat");
         ProdDaysAvail[month]          := (23, 20, 23, 22);
         InvtCost[plant, product]      := DATAFILE("InvtCost.dat");

         InvtCapacity[plant]           := (800, 400, 500, 400);
         ShipCost[fromplant, toplant]  := DATAFILE ("ShipCost.dat");

     VARIABLES
         Produce[plant, product,month]      -> Prod;
         Inventory[plant, product, month]   -> Invt;
         Sales[plant,product, month]        -> Sale;
         Ship[product, month, fromplant, toplant]
             WHERE (fromplant <> toplant);

     MACROS
         TotalRevenue  := SUM(plant, product,month: Price * Sales);
         TotalProdCost := SUM(plant, product, month: ProdCost* Produce);
         TotalInvtCost := SUM(plant, product, month: InvtCost * Inventory);
         TotalShipCost := SUM(product, month, fromplant,toplant: ShipCost * Ship);
         TotalCost     := TotalProdCost + TotalInvtCost + TotalShipCost;

     MODEL

         MAX Profit= TotalRevenue - TotalCost;

     SUBJECT TO
         ProdCapacity[plant, month] -> PCap:
             SUM(product: Produce / ProdRate)  <=  ProdDaysAvail;

         PlantBal[plant, product, month] -> PBal:
               Produce + Inventory[month-1]
             + SUM(fromplant:Ship[fromplant, toplant:=plant])
            =
               Sales + Inventory
             + SUM(toplant: Ship[fromplant:=plant,toplant]);

         MaxInventory[plant, month] -> MaxI:
             SUM(product: Inventory)  <=  InvtCapacity;

     BOUNDS
         Sales < Demand ;

     END
    

Enter New Elements to the Model Step by Step

Step 1: Start MPL and Create a New Model

  1. Start the MPL application.

  2. Choose File | Open and open the model from the previous session Planning5.mpl.

  3. Choose File | Save As to save it as a new model file Planning6.mpl.

Step 2: Change the Title for the Model

Change the title for the model to reflect that you are working with the Planning6 model:

     TITLE
        Production_Planning6;
    

Step 3: Add Alias Indexes for the Source and Destination Plants to the Model

In this session, you are going to expand the model to allow shipments between the plants. Alias indexes are useful when you need to define a vector, which refers to the same index more than once, as a subscript. Alias indexes are an exact copy of a previously defined index.

In this case, you are creating a vector variable representing how much to ship between the plants. Which means you need two alias indexes to represent the source, and destination plants. Add the following definitions for the two new alias indexes that you will call fromplant and toplantat the end of the INDEX section:

      INDEX
         product      := (A1, A2, A3);
         month        := (Jan, Feb, Mar, Apr);
         plant        := (p1, p2,p3, p4);
         fromplant    := plant;
         toplant      := plant;
    

Step 4: Update the 'Demand' Data Vector to Include the 'plant' Index

We now have a different demand for each plant. The definition for the Demand data vector needs to be upgraded to include the plant index. In the model editor, add the index plant to the Demand data vector and change the file name to Demand6.dat.

     DATA
        Price[product]                := (120.00, 100.00, 115.00);
        Demand[plant, product, month] := DATAFILE("Demand6.dat");
    

Since the Demand vector now has three indexes, the data file for it needs to be updated. Therefore, you are creating a new data file called 'Demand6.dat' using the data values from the dbtable in the problem description given earlier in this session. Enter the data values to the data file as follows:

     !
     ! Demand6.dat - Demand for each product and each plant
     !
     ! Demand[plant,product,month]:
     !
     !           Jan    Feb    Mar    Apr
     !          ---------------------------
     !plant 1:
                 4300,  4200,  6400,  5300,
                 4500,  5400,  6500,  7200,
                 5400,  6700,  7800,  8200,
     !plant 2:
                 5100,  6200,  5400,  7600,
                 6300,  7100,  5200,  6300,
                 4800,  6500,  5000,  7200,
     !plant 3:
                 4100,  6100,  4700,  5800,
                 5300,  5200,  5700,  4100,
                 4200,  4100,  5200,  6300,
     !plant 4:
                 4300,  4100,  5300,  4500,
                 5300,  6400,  4200,  6200,
                 5600,  5200,  3800,  4100
     

When you have three dimensional data in data files, list the data values in same order that the indexes were defined in data vector for the model. For example, in the above Demand6 data file the leftmost index is the plant index, followed by the product and the month index.

Step 5: Upgrade the 'InvtCost' Data Vector and the 'InvtCapacity' Data Constant to Include the 'plant' Index

Since you can now store inventory at each plant, you need to update the inventory cost and the inventory capacity data to include the plant index. In the model editor, add the index plant to the declaration of the InvtCost data vector and the InvtCapacity data constant. Then, for the InvtCost data vector remove the list of numbers and replace it with the keyword DATAFILE and the file name 'InvtCost.dat.' For the InvtCapacity remove the single value 800 and replace it with the list of four values, one for each plant, taken from the problem description earlier in this session.

     InvtCost[plant, product] := DATAFILE("InvtCost.dat");
     InvtCapacity[plant]      := (800, 400, 500, 400);
    

Next, you need to create a new data file called 'InvtCost.dat' using the cost values from the inventory cost dbtable in the problem description. Enter the data values into the data file as follows:

      !
      ! InvtCost.dat - Inventory cost per item a month
      !
      ! InvtCost[plant,product]:
      !
      !         A1    A2    A3
      !     -----------------------
               8.50,  7.00, 6.50
               9.80,  9.80, 9.80
               7.50,  7.50, 7.50
               9.30,  8.00, 6.50
    

Step 6: Add a Data Vector for the Shipping Costs

There are certain costs involved in shipping products between plants. In the model editor, add a new data vector called ShipCost defined over the two alias indexes fromplant and toplant followed by the keyword DATAFILE and the file name ShipCost.dat.

     ShipCost[fromplant, toplant] := DATAFILE("ShipCost.dat");
    

Next, you need to create a new data file called ShipCost.dat containing the cost figures for shipping between the plants provided in the problem description. Enter the data values for the data file as follows:

     !
     ! ShipCost.dat - Shipping costs from plant to plant
     !
     ! ShipCost[fromplant, toplant]
     !
                0, 15.00, 21.00, 13.00,
            16.00,     0, 12.00, 12.00,
            14.00, 17.00,     0, 15.00,
            21.00, 13.00, 10.00,     0,
    

Step 7: Add a Variable Vector for Shipments Between Plants

We are allowing shipping between the plants, therefore, you need to create a new variable that decides how much is to be shipped of each product per month. This variable vector will be defined over the alias indexes fromplant and toplant as well as the product and the month index. Add the following definition for the Ship variable vector to the VARIABLES section:

     Ship[product, month, fromplant, toplant]
        WHERE (fromplant <> toplant);
    

Since we do not want to ship a product from a plant back to the same plant, we are using a WHERE condition to remove all of the vector elements where the source plant is the same as the destination plant.

Step 8: Add the Total Shipping Cost to the Objective Function

In the MACROS section add a new macro definition for the total shipping cost called TotalShipCost and update the TotalCost macro to include the new macro as follows:

      TotalShipCost := SUM(product, month, fromplant, toplant: ShipCost * Ship);
      TotalCost     := TotalProdCost + TotalInvtCost + TotalShipCost;
    

Note that the actual objective function definition does not need to be changed as the TotalCost macro contains all the changes.

Step 9: Upgrade the Inventory Balance Constraint to a Plant Balance Constraint by Adding the 'Ship' Variable

Since we now have shipments allowed between the plants, you need to upgrade the inventory balance constraint from the previous model to a plant balance constraint. First, change the name of the constraint from InvtBal to PlantBal and add the index plant to the declaration. Next, since the constraint is now declared over the index plant we do not have to sum over the index plant for the Produce variable anymore.

     PlantBal[plant, product, month] -> PBal:

               Produce + Inventory[month-1]
             + SUM (fromplant:Ship[fromplant, toplant:=plant])
           =
               Sales + Inventory
             + SUM(toplant: Ship[fromplant:=plant, toplant]);
    

On the left hand side, where we bring together everything that is going into the plant, add a summation to add together all the shipments from each of the other plants to the current plant for the constraint. Inside the summation, enter the Ship variable with the destination plant taking the value of the current plant index.

On the right hand side, where we bring together everything that is going out of the plant add another summation to add together all the shipments from the current plant to each of the other plants. Inside the summation enter the Ship variable this time with the source plant taking the value of the current plant index for the constraint.


Solve the Model and Analyze the Solution

The next step is to solve the Planning6 model by choosing Solve CPLEX from the Run menu. If everything goes well MPL will display the message "Optimal Solution Found". If there is an error message window with a syntax error please check and compare the formulation you entered with the model detailed earlier in this session.

You will use the model definitions window again as in Session 5 to look at the parts of the solution that we are interested in. To open the model definitions window for the Planning6 model choose Model Definitions from the View menu.

The Model Definitions Tree Window for the Planning6 Model

To look at the values for the Produce variable, either double-click on the Produce item in the tree, or select it and then press the View button. This will display a window containing only the values for the Produce variable as shown below.

     VARIABLE Produce[plant,product,month] :

        plant  product  month           Activity     Reduced Cost
       -----------------------------------------------------------
        p1     A1       Jan              4300.0              0.0
        p1     A1       Feb              4200.0              0.0
        p1     A1       Mar              6400.0              0.0
        p1     A1       Apr              5300.0              0.0
        p1     A2       Jan              1080.0              0.0
        p1     A3       Jan              5400.0              0.0
        p1     A3       Feb              5220.0              0.0
        p1     A3       Mar              4590.0              0.0
        p1     A3       Apr              5130.0              0.0
        p2     A1       Jan              5100.0              0.0
        p2     A1       Feb              6200.0              0.0
        p2     A1       Mar              5400.0              0.0
        p2     A1       Apr              7600.0              0.0
        p2     A2       Jan              6177.3              0.0
        p2     A2       Feb              3927.3              0.0
        p2     A2       Mar              5931.8              0.0
        p2     A2       Apr              3681.8              0.0
        p3     A1       Jan              4100.0              0.0
        p3     A1       Feb              6100.0              0.0
        p3     A1       Mar              4700.0              0.0
        p3     A1       Apr              5800.0              0.0
        p3     A3       Jan              4166.7              0.0
        p3     A3       Feb              1933.3              0.0
        p3     A3       Mar              3766.7              0.0
        p3     A3       Apr              2733.3              0.0
        p4     A1       Jan              3850.0              0.0
        p4     A1       Feb              2828.6              0.0
        p4     A1       Mar              5300.0              0.0
        p4     A1       Apr              4500.0              0.0
        p4     A3       Jan              5600.0              0.0
        p4     A3       Feb              5200.0              0.0
        p4     A3       Mar              4677.3              0.0
        p4     A3       Apr              4836.4              0.0
       -----------------------------------------------------------
    

As you can see, the production is now distributed between the different plants in a more cost effective manner. Certain products are clearly better to produce at particular plants due to taking into account the production cost and the shipping cost. For example, product A2 is produced in plants p1 and p2, but not p3 and p4, while product A3 is produced in plants p1, p3 and p4. Product A1 is most economically produced in all of the plants.

If you go to the tree window again and open up a window for the Ship variable you will get the following solution values:

     VARIABLE Ship[product,month,fromplant,toplant] :

        product  month  fromplant  toplant           Activity     Reduced Cost
       ------------------------------------------------------------------------
        A2       Mar    p2         p4                  331.8              0.0
        A3       Mar    p4         p3                  877.3              0.0
        A3       Apr    p4         p3                  736.4              0.0
       ------------------------------------------------------------------------
    

As you can see the model proposes that we ship product A2 from plant p2 to plant p4. In the same manner, product A3 is shipped from plant p4 to p3. Clearly, plants p2 and p4 have extra capacity at lower cost that can be used to produce goods that plants p4 and p3 need.


Back To Top | Maximal Home Page | Overview | Previous Page | Next Page